using System.Configuration;
using System.Web;
using System.Xml;
using System;
using System.Globalization;

/*	-----------------------------------------------------------------------	
	Copyright:	umlungu consulting (pty) ltd
	Author:		Alan Benington	
	Started:	2005-09-08
	Status:		release
	Version:	2.0.0
	Build:		20070713
	License:	GNU General Public License
	-----------------------------------------------------------------------	*/

/*	----------------------------------------------------------------------------------------------------	
	Development Notes:
	==================
	20070621:	Jacked from precisionX (x_node class):
				It is intended to be a general purpose utility class that describes a tree of nodes and 
				provides access to the nodes.
				Some differences between x_node and x_tree:
				- x_tree is derived from XmlDocument as the natural description of a node tree is xml.
				- Method names have changed and most protected methods have become public.
				- The nodes in the tree can be of any name (the 'id'). Selection methods accomodate the id.
				-
	20070713:	Extended AddNode for multiple attributes
	----------------------------------------------------------------------------------------------------	*/

namespace umlungu.engineX {
	/// <summary>
	/// The EngineX node class.
	/// <para>The base class for all nodes.</para>
	/// <para>A node represents any level in an EngineX hierarchy</para>
	/// </summary>
	public class x_tree : XmlDocument {
		#region Invisible properties
		#endregion

		#region Constants
		private const string DEFAULT_NAME = "x_tree";
		private const string DEFAULT_NODE_ID = "node";

		// NB: If any of these constants change, so must the select constants
		private const string NODE_ID = "id";
		private const string NODE_NAME = "name";
		private const string NODE_TYPE = "type";
		private const string NODE_TITLE = "title";
		private const string NODE_REFERENCE = "reference";
		private const string NODE_CHANGED = "changed";
		private const string NODE_SAVED = "saved";
		private const string NODE_EXPANDED = "expanded";
		// NB: See note above
		private const string SELECT_NAME = "//{0}[@name='{1}']";
		private const string SELECT_ID = "//{0}[@id='{1}']";
		private const string SELECT_PARENT_NAME = "//{0}[@name='{1}']/{0}[@name='{2}']";
		private const string SELECT_TYPE = "[@type='{0}']";
		private const string SELECT_NOTTYPE = "[@type!='{0}']";
		private const string SELECT_TYPES = "//{0}[@type='{1}']";
		private const string SELECT_CHILD_TYPE = "{0}[@type='{1}']";
		private const string SELECT_CHILD_NAME = "{0}[@name='{1}']";
		private const string SELECT_CHILD_NOTTYPE = "{0}[@type!='{1}']";
		private const string SELECT_REFERENCE = "{0}[@reference]";
		private const string SELECT_NOTID = "//{0}[not(@id)]";
		private const string SELECT_ISID = "//{0}[@id]";
		private const string SELECT_CHILD = "//{0}[@name='{1}']/{2}[@name='{3}']";
		private const string SELECT_DESCENDANT = "{0}[@name='{1}']//{0}[@name='{2}']";
		private const string SELECT_TYPE_DESCENDANT = ".//{0}[@type='{1}'][@name='{2}']";
		#endregion

		#region Visible properties
		private string type;
		private string reference;

		private string id;
		/// <summary>The id</summary>
		/// <value>ID of the node</value>
		public string ID {
			get { return (id); }
			set { id = value; }
		}

		private string title;
		/// <summary>The title</summary>
		/// <value>Title of the node</value>
		public string Title {
			get { return (title); }
			set { title = value; }
		}

		private string name = "TreeX";
		/// <summary>The name</summary>
		/// <value>name string</value>
		new public string Name {
			get { return (name); }
			set { name = value; }
		}

		private string basedirectory = "\\";
		/// <summary>The basedir directory</summary>
		/// <value>BaseDirectory of the node</value>
		public string BaseDirectory {
			get { return (basedirectory); }
			set { basedirectory = value; }
		}

		private string datadirectory = "data";
		/// <summary>The datadirectory directory</summary>
		/// <value>DataDirectory of the node</value>
		public string DataDirectory {
			get { return (datadirectory); }
			set { datadirectory = value; }
		}
		#endregion

		#region Constructors/Destructors
		/// <overloads>Constructor</overloads>
		/// <summary>Default constructor</summary>
		public x_tree() {
			initialise();
		}
		/// <summary>Constructor specifying node ID</summary>
		/// <param name="NodeID">The name of a node element in the node tree</param>
		public x_tree(string NodeID) {
			initialise(NodeID);
		}
		/// <summary>Constructor specifying node ID and file to Load</summary>
		/// <param name="NodeID">The name of a node element in the node tree</param>
		/// <param name="FileName">Node definition file</param>
		public x_tree(string NodeID, string FileName) {
			initialise(NodeID);
			Acquire(FileName);
		}
		/// <summary>Constructor specifying node ID and file to Load + flag to indicate with/without expansion of nodes in tree</summary>
		/// <param name="NodeID">The name of a node element in the node tree</param>
		/// <param name="FileName">Node definition file</param>
		/// <param name="Expand">flad to indicate expansion of the node tree</param>
		public x_tree(string NodeID, string FileName, bool Expand) {
			initialise(NodeID);
			Acquire(FileName, Expand);
		}
		#endregion

		#region Public methods
		/// <overloads>Acquires the tree of nodes from a source</overloads>
		/// <summary>Source is an xml file</summary>
		public void Acquire(string filename) {
			Acquire(filename, false);
		}
		/// <summary>Source is an xml file</summary>
		public void Acquire(string filename, bool expandflag) {
			Load(filename);
			if (expandflag) {
				// expand all reference nodes...
				expand();
			}
			name = DocumentElement.Name;
		}
		/// <summary>Source is an xml element</summary>
		public void Acquire(XmlElement xmlnode) {
			load(xmlnode);
			name = DocumentElement.Name;
		}

		/// <overloads>Persists the node tree to a source</overloads>
		/// <summary>Source is an xml file</summary>
		public void Persist(string filename, string type) {
			Persist(filename, type, null, false);
		}
		/// <summary>Source is an xml file</summary>
		public void Persist(string filename, string type, bool saveall) {
			Persist(filename, type, null, saveall);
		}
		/// <summary>Source is an xml file</summary>
		public void Persist(string filename, string type, string name) {
			Persist(filename, type, name, true);
		}
		/// <overloads>Persists the node tree to a source</overloads>
		/// <summary>Source is an xml file</summary>
		public void Persist(string filename, string type, string name, bool saveall) {
			foreach (XmlNode nde in GetNodes(type, saveall)) {
				XmlElement ndeel = nde as XmlElement;
				string typ = ndeel.GetAttribute(NODE_TYPE);
				string nme = ndeel.GetAttribute(NODE_NAME);
				string cng = ndeel.GetAttribute(NODE_CHANGED);	// look for any element with changed attribute?
				string rfr = ndeel.GetAttribute(NODE_REFERENCE);
				string exp = ndeel.GetAttribute(NODE_EXPANDED);
				string sav = ndeel.GetAttribute(NODE_SAVED);
				if (rfr != "") {
					if (name == null || nme == name) {
						if (cng == "yes" || saveall) {
							ndeel.RemoveAttribute(NODE_CHANGED);
							foreach (XmlNode desc in ndeel.SelectNodes(String.Format(".//{0}[@{1}]", id, NODE_CHANGED))) {
								((XmlElement)desc).RemoveAttribute(NODE_CHANGED);
							}
							XmlDocument thisnde = new XmlDocument();
							thisnde.LoadXml(nde.OuterXml);
							thisnde.DocumentElement.RemoveAttribute(NODE_REFERENCE);
							thisnde.Save(String.Concat(BaseDirectory, "\\", DataDirectory, "\\", rfr, ".xml"));
							sav = DateTime.Now.ToString("s", DateTimeFormatInfo.InvariantInfo);
						}
					}
					ndeel.RemoveAll();
					//ndeel.RemoveAllAttributes();
					ndeel.SetAttribute(NODE_TYPE, typ);
					ndeel.SetAttribute(NODE_NAME, nme);
					ndeel.SetAttribute(NODE_REFERENCE, rfr);
					ndeel.SetAttribute(NODE_SAVED, sav);
					//ndeel.SetAttribute(NODE_EXPANDED, exp);
				}
			}
			Save(filename);
		}

		/// <overloads>Add a node to the tree</overloads>
		/// <summary>Append node to an existing parent node</summary>
		public XmlNode AddNode(XmlNode parent, string type, string name) {
			return parent.AppendChild(AddNode(type, name));
		}
		/// <summary>Append node to an existing parent node</summary>
		public XmlNode AddNode(XmlNode parent, string type, string name, string attrname, string attrval) {
			return parent.AppendChild(AddNode(type, name, attrname, attrval));
		}
		/// <summary>Append node to an existing parent node</summary>
		public XmlNode AddNode(XmlNode parent, string type, string name, string attrname1, string attrval1, string attrname2, string attrval2) {
			return parent.AppendChild(AddNode(type, name, attrname1, attrval1, attrname2, attrval2));
		}
		/// <summary>Append node to an existing parent node</summary>
		public XmlNode AddNode(XmlNode parent, string type, string name, string refname) {
			return parent.AppendChild(AddNode(type, name, refname));
		}
		/// <summary>Adds node but does not append</summary>
		public XmlNode AddNode(string type, string name) {
			XmlElement newnode = CreateElement(id);
			newnode.SetAttribute(NODE_TYPE, type);
			newnode.SetAttribute(NODE_NAME, name);
			_SetChanged(newnode);
			return newnode;
		}
		/// <summary>Adds node (with a reference) but does not append</summary>
		public XmlNode AddNode(string type, string name, string refname) {
			XmlElement newnode = CreateElement(id);
			newnode.SetAttribute(NODE_TYPE, type);
			newnode.SetAttribute(NODE_NAME, name);
			newnode.SetAttribute(NODE_REFERENCE, refname);
			_SetChanged(newnode);
			return newnode;
		}
		/// <summary>Adds node with attribute, but does not append</summary>
		public XmlNode AddNode(string type, string name, string attrName, string attrValue) {
			XmlElement newnode = CreateElement(id);
			newnode.SetAttribute(NODE_TYPE, type);
			newnode.SetAttribute(NODE_NAME, name);
			newnode.SetAttribute(attrName, attrValue);
			_SetChanged(newnode);
			return newnode;
		}
		/// <summary>Adds node with 2 attrs, but does not append</summary>
		public XmlNode AddNode(string type, string name, string attrName1, string attrValue1, string attrName2, string attrValue2) {
			XmlElement newnode = CreateElement(id);
			newnode.SetAttribute(NODE_TYPE, type);
			newnode.SetAttribute(NODE_NAME, name);
			newnode.SetAttribute(attrName1, attrValue1);
			newnode.SetAttribute(attrName2, attrValue2);
			_SetChanged(newnode);
			return newnode;
		}

		/// <overloads>Get the xml definition of a node</overloads>
		/// <summary>Node identified by name</summary>
		public XmlNode GetNode(string name, bool expandflag) {
			string select = String.Format(SELECT_NAME, id, name);
			return getNode(select, expandflag);
		}
		/// <summary>Node identified by parent type and name and descendant ID</summary>
		public XmlNode GetNode(string type, string name, string descendantID, bool expandflag, bool childflag) {
			string selectParent = String.Format(SELECT_NAME, id, name);
			string selectDescendant = isParentNode(type, selectParent, descendantID)? "" : String.Format(SELECT_ID, id, descendantID);
			return getNode(type, selectParent, selectDescendant, expandflag, childflag);
		}
		/// <summary>Node identified by type and name</summary>
		public XmlNode GetNode(string type, string name, bool expandflag) {
			string select = String.Format(SELECT_NAME, id, name);
			return getNode(type, select, expandflag);
		}
		/// <summary>Node identified by type, parent name and name</summary>
		public XmlNode GetNode(string type, string parent, string name, bool expandflag) {
			string select = String.Format(SELECT_PARENT_NAME, id, parent, name);
			return getNode(type, select, expandflag);
		}
		/// <summary>Node within a branch identified by branch type and node name</summary>
		public XmlNode GetNode(XmlNode parent, string type, string name, bool expandflag) {
			string select = String.Format(SELECT_TYPE_DESCENDANT, id, type, name);
			return getNode(parent, select, expandflag);
		}
		/// <summary>Node within a branch identified by branch type and node name</summary>
		public XmlNode GetNode(XmlNode parent, string type, bool expandflag) {
			string select = String.Format(SELECT_CHILD_TYPE, id, type);
			return getNode(parent, select, expandflag);
		}

		/// <overloads>Gets a child of a node. Child nodes are not identified by id, as they are named, eg 'property', 'document' etc</overloads>
		/// <summary>Child node identified by parent name and name</summary>
		public XmlNode GetChild(string parent, string child, string name, bool expandflag) {
			string select = String.Format(SELECT_CHILD, id, parent, child, name);
			return getNode(select, expandflag);
		}
		public XmlNode GetChild(XmlNode parent, string name) {
			return GetChild(parent, name, false);
		}
		public XmlNode GetChild(XmlNode parent, string name, bool expandflag) {
			string select = String.Format(SELECT_CHILD_NAME, id, name);
			return getNode(parent, select, expandflag);
		}

		/// <summary>Gets a list of nodes of a particular type</summary>
		public XmlNodeList GetNodes(string type, bool expandflag) {
			return getNodeList(type, expandflag);
		}
		public XmlNodeList GetNodes(string type, XmlNode parent) {
			return GetNodes(type, parent, false);
		}
		public XmlNodeList GetNodes(string type, XmlNode parent, bool expandflag) {
			return getNodeList(type, parent, expandflag);
		}

		public string GetNodeString(string type, string parent, string name, bool expandflag) {
			string select = String.Format(SELECT_PARENT_NAME, id, parent, name);
			return getNodeString(type, select, expandflag);
		}

		/// <overloads>Loads the xml definition of a node</overloads>
		/// <summary>Node identified by type and name</summary>
		public void AcquireNode(string type, string name, bool expandflag) {
			string select = String.Format(SELECT_NAME, id, name);
			getNode(type, select, expandflag);
		}

		/// <overloads>Clones chilren of a node to a child. Excludes the nodes that are the same type as the new node</overloads>
		/// <summary>Copies cloned to parent node</summary>
		public XmlNode CloneToChild(XmlNode ToClone, XmlNode Parent, string ChildType, string ChildName) {
			XmlDocumentFragment copyOf = CreateDocumentFragment();
			foreach (XmlNode clone in ToClone.SelectNodes(String.Format(SELECT_CHILD_NOTTYPE, id, ChildType))) {
				copyOf.AppendChild(clone.CloneNode(true));
			}
			XmlNode newChild = AddNode(ChildType, ChildName);
			newChild.AppendChild(copyOf);
			return Parent.AppendChild(newChild);
		}
		/// <summary></summary>
		public XmlDocumentFragment CloneToChild(XmlNode ToClone, string IncludedType) {
			XmlDocumentFragment copyOf = CreateDocumentFragment();
			foreach (XmlNode clone in ToClone.SelectNodes(String.Format(SELECT_CHILD_TYPE, id, IncludedType))) {
				copyOf.AppendChild(clone.CloneNode(true));
			}
			return copyOf;
		}

		public void Expanded(XmlNode node) {
			XmlElement thisnode = node as XmlElement;
			thisnode.SetAttribute(NODE_EXPANDED, "yes");
		}
		#endregion

		#region Protected methods
		/// <summary>Loads the node properties from an xml element</summary>
		protected void load(XmlElement source) {
			name = source.GetAttribute(NODE_NAME);
			type = source.GetAttribute(NODE_TYPE);
			reference = source.GetAttribute(NODE_REFERENCE);
			title = (source.SelectSingleNode(NODE_TITLE) != null) ? source.SelectSingleNode(NODE_TITLE).InnerText : "";
		}
		/// <summary>Removes the id attributes</summary>
		protected void removeIDs() {
			foreach (XmlNode nde in SelectNodes(String.Format(SELECT_ISID, id))) {
				((XmlElement) nde).RemoveAttribute(NODE_ID);
			}
		}
		protected void _SetChanged(XmlElement node) {
			node.SetAttribute(NODE_CHANGED, "yes");
		}
		#endregion

		#region Private methods
		/// <overloads>Initialise the object on creation</overloads>
		/// <summary>Initialise with default name and id</summary>
		private void initialise() {
			initialise(DEFAULT_NODE_ID, DEFAULT_NAME);
		}
		/// <summary>Initialise with specified id and default name</summary>
		private void initialise(string nodeID) {
			initialise(nodeID, DEFAULT_NAME);
		}
		/// <summary>Initialise with specified id and name</summary>
		private void initialise(string nodeID, string nodeName) {
			id = nodeID;
			name = nodeName;
		}

		private bool isParentNode(string type, string selParent, string descendantID) {
			string select = String.Concat(selParent, String.Format(SELECT_TYPE, type));
			if (descendantID == "0")
				return true;	// special case
			XmlElement thisel = (XmlElement)SelectSingleNode(select);
			if (thisel == null)
				throw (new x_exception("Error_getNode", String.Concat("Node not found:", select)));
			else
				return (thisel.GetAttribute(NODE_ID) == descendantID);
		}

		/// <overloads>Get the xml definition of a node and optionally expands the node</overloads>
		/// <summary>Node identified only by selection string</summary>
		private XmlNode getNode(string select, bool expandflag) {
			XmlNode thisnode = SelectSingleNode(select);
			if (expandflag && thisnode != null)
				expand(thisnode);
			return thisnode;
		}
		/// <summary>Node identified by type and selection string</summary>
		private XmlNode getNode(string type, string select, bool expandflag) {
			select = String.Concat(select, String.Format(SELECT_TYPE, type));
			XmlNode thisnode = SelectSingleNode(select);
			if (expandflag && thisnode != null)
				expand(thisnode);
			return thisnode;
		}
		/// <summary>Node within a node identified by selection string</summary>
		private XmlNode getNode(XmlNode node, string select, bool expandflag) {
			XmlNode thisnode = node.SelectSingleNode(select);
			if (expandflag && thisnode != null)
				expand(thisnode);
			return thisnode;
		}
		/// <summary>Node identified by parent type and selection string and descendant selection string</summary>
		private XmlNode getNode(string type, string selParent, string selDesc, bool expandflag, bool childflag) {
			if (DocumentElement == null)
				throw(new x_exception("Error_getNode", "Source document not loaded"));	
			string select = String.Concat(selParent, String.Format(SELECT_TYPE, type), selDesc);
			XmlNode thisnode = SelectSingleNode(select);
			if (thisnode == null)
				throw(new x_exception("Error_getNode", String.Concat("Node not found:", select)));	
			
			if (!childflag) {
				XmlNodeList nodes = thisnode.SelectNodes(id);
				foreach (XmlNode node in nodes)
					thisnode.RemoveChild(node);
			}
			if (expandflag && thisnode != null)
				expand(thisnode);
			return thisnode;
		}

		/// <overloads>Gets a list oif nodes of a particular type</overloads>
		/// <summary>Gets nodes (does not expand first)</summary>
		/// <param name=NODE_TYPE>The type of node identified by the 'type' attribute</param>
		private XmlNodeList getNodeList(string type) {
			return getNodeList(type, false);
		}
		/// <summary>Gets nodes with optional node expansion</summary>
		/// <param name=NODE_TYPE>The type of node identified by the 'type' attribute</param>
		/// <param name="expandflag">A flag indicating if nodes are to be expanded</param>
		private XmlNodeList getNodeList(string type, bool expandflag) {
			if (expandflag)
				expand();
			return SelectNodes(String.Format(SELECT_TYPES, id, type));
		}
		/// <summary>Gets nodes with optional node expansion</summary>
		/// <param name=NODE_TYPE>The type of node identified by the 'type' attribute</param>
		/// <param name="expandflag">A flag indicating if nodes are to be expanded</param>
		private XmlNodeList getNodeList(string type, XmlNode selParent, bool expandflag) {
			if (expandflag)
				expand();
			return selParent.SelectNodes(String.Format(SELECT_CHILD_TYPE, id, type));
		}

		/// <overloads>Expand node references</overloads>
		/// <summary>Use default BaseDirectory and DataDirectory directory values</summary>
		private void expand() {
			expand(DocumentElement, BaseDirectory, DataDirectory);
		}
		/// <summary>Use default BaseDirectory and DataDirectory directory values</summary>
		private void expand(XmlNode thisnode) {
			expand(thisnode, BaseDirectory, DataDirectory);
		}
		/// <summary>Use specified basedir and datadir directory values</summary>
		private void expand(XmlNode thisnode, string basedir, string datadir) {
			string rfr = ((XmlElement)thisnode).GetAttribute(NODE_REFERENCE);
			string exp = ((XmlElement)thisnode).GetAttribute(NODE_EXPANDED);
			if (rfr != "") {
				if (exp == "") {	// not already expanded
					XmlDocument refdoc = new XmlDocument();
					refdoc.Load(String.Concat(basedir, "\\", datadir, "\\", rfr, ".xml"));
					thisnode.InnerXml = refdoc.DocumentElement.InnerXml;
					((XmlElement)thisnode).SetAttribute(NODE_EXPANDED, "yes");
				}
			}
			XmlNodeList refnodes = thisnode.SelectNodes(String.Format(SELECT_REFERENCE, id));
			foreach (XmlNode refnode in refnodes) {
				// call expand to recursively expand all nodes...
				expand(refnode, basedir, datadir);
			}

			// Iterate through all nodes and add a unique, sequential id attribute
			//XmlNodeList idnodes = thisnode.SelectNodes(String.Format(SELECT_NOTID, id));
			//int i=0;
			//foreach (XmlNode idnode in idnodes) {
			//    ((XmlElement)idnode).SetAttribute(NODE_ID, (i++).ToString());
			//}
		}

		private string getNodeString(string type, string select, bool expandflag) {
			return String.Concat(select, String.Format(SELECT_TYPE, type));
		}
		#endregion
	}
}
